home *** CD-ROM | disk | FTP | other *** search
/ The 640 MEG Shareware Studio 2 / The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO / pascal / totdoc.zip / CHAPT19.TXT < prev    next >
Text File  |  1991-02-11  |  20KB  |  456 lines

  1.                                                                      Customizing
  2.                                                                           Linked
  3.                                                                            Lists
  4.  
  5.  
  6.  
  7.          "As we acquire knowledge, things do not become more comprehensible, but
  8.          more mysterious."
  9.  
  10.                                                                      Will Durant
  11.  
  12.  
  13.  
  14.          Doubly-linked lists are ideal for managing large lists of data, they
  15.          are memory efficient and fast. The only problem is they are compli-
  16.          cated! Fortunately, the Toolkit provides a very easy to use doubly-
  17.          linked list object called DLLOBJ. DLLOBJ is an abstract object designed
  18.          specifically to simplify the task of extending the object. In this
  19.          chapter, techniques for developing a custom doubly-linked list for
  20.          managing records is discussed.
  21.  
  22.          You might want to consider re-reading the section on linked list theory
  23.          (page 9-1) before proceeding.
  24.  
  25.  
  26. DLLOBJ
  27.          Unlike many objects, you do not need to understand very much about
  28.          DLLOBJ to create descendant objects. All the following methods (which
  29.          were described in chapter 9) are unaffected by the type of data stored
  30.          in the list, and do not need to be modified in descendant objects:
  31.  
  32.          procedure   Advance(Amount:longint);
  33.          procedure   Retreat(Amount:longint);
  34.          function    NodePtr(NodeNumber:longint): DLLNodePtr;
  35.          procedure   Jump(NodeNumber:longint);
  36.          procedure   ShiftActiveNode(NewNode: DLLNodePtr; NodeNumber: longint);
  37.          procedure   DelNode(Node:DLLNodePtr);
  38.          procedure   DelAllStatus(BitPos:byte;On:boolean);
  39.          function    TotalNodes: longint;
  40.          function    ActiveNodeNumber: longint;
  41.          function    ActiveNodePtr: DLLNodePtr;
  42.          function    StartNodePtr: DLLNodePtr;
  43.          function    EndNodePtr: DLLNodePtr;
  44.          procedure   EmptyList;
  45.          procedure   Sort(SortID:shortint;Ascending:boolean);
  46.          procedure   SwapNodes(Node1,Node2:DLLNodePtr);
  47.  
  48.          The DLLOBJ stores un-typed data in binary format. You can literally
  49.          stored any type of data in a DLLOBJ list. The following methods add and
  50.          modify data in a list:
  51.  
  52.  
  53. 19-2                                                       Extending the Toolkit
  54. --------------------------------------------------------------------------------
  55.  
  56.          function Add(var TheData;Size:longint): integer;
  57.          function Change(Node:DLLNodePtr;var TheData;Size:longint): integer;
  58.          function InsertBefore(Node:DLLNodePtr;var TheData;Size:longint): inte-
  59.          ger;
  60.  
  61.  
  62.          Each of these three methods are passed an untyped parameter and a lon-
  63.          gint indicating the size of the data. In descendant objects, you will
  64.          call these methods to manipulate the data in the list. The following
  65.          methods return information about the data stored in the list:
  66.          procedure   Get(var TheData);
  67.          procedure   GetNodeData(Node:DLLNodePtr;Var TheData);
  68.          function    GetNodeDataSize(Node:DLLNodePtr):longint;
  69.          function    GetMaxNodeSize: longint;
  70.  
  71.  
  72.          The most used method is GetNodeData, which will update a passed un-
  73.          typed parameter with the data stored in the list. It is the fundamental
  74.          way for a descendant object to get data from the list.
  75.  
  76.  
  77. Extending DLLOBJ
  78.  
  79.          The main reason for extending DLLOBJ is to make the new object manage a
  80.          specific type of data. The Toolkit includes the descendant StrDLLOBJ,
  81.          which is specifically designed to store strings, and FileDLLOBJ, which
  82.          stores DOS file details.
  83.          In this section, DLLOBJ will be extended and customized to store a
  84.          record. To illustrate the principles involved, we will create a new
  85.          object RecordDLLOBJ to store the following record data:
  86.  
  87.          RecordInfo = record
  88.             FirstName: string[15];
  89.             LastName: string[15];
  90.             Company: string[20];
  91.             Tel: string[10];
  92.             CumDollarsSpent: real;
  93.             LastOrder: longint;
  94.             Comments: string[40];
  95.          end;
  96.  
  97.          The main methods that need to be customized are the data manipulation
  98.          methods, i.e. Add, Change and InsertBefore. If you want to display the
  99.          object in a Browse or List window you must also customize the GetStr
  100.          virtual method. GetStr is called by the browse and list objects, and is
  101.          simply a function which returns the data stored at a node in string
  102.          form. The fifth method which usually needs to be customized is Wron-
  103.  
  104.  
  105.  
  106. Customizing Linked Lists                                                    19-3
  107. --------------------------------------------------------------------------------
  108.  
  109.          gOrder. This method provides the Sort method with the information
  110.          needed to sort the data, and is discussed in a later section. The new
  111.          object would therefore be declared as follows:
  112.  
  113.          RecordListOBJ = object (DLLOBJ)
  114.             constructor Init;
  115.             function    Add(Rec:RecordInfo): integer;
  116.             function    Change(Node:DLLNodePtr;Rec:RecordInfo): integer;
  117.             function    InsertBefore(Node:DLLNodePtr;Rec:RecordInfo): integer;
  118.             function    WrongOrder(Node1,Node2:DLLNodePtr;
  119.                                    Asc:boolean): boolean;        VIRTUAL;
  120.             function    GetStr(Node:DLLNodePtr;
  121.                                Start,Finish: longint):string;    VIRTUAL;
  122.             destructor  Done;                                    VIRTUAL;
  123.          end; {RecordListOBJ}
  124.  
  125.          Notice that Add, Change and InsertBefore are each passed a variable of
  126.          type RecordInfo. All these methods need to do is call their correspond-
  127.          ing DLLOBJ method and pass the record as an untyped parameter together
  128.          with the record size. The three methods would be implemented as
  129.          follows:
  130.  
  131.          function RecordDLLOBJ.Add(Rec:RecordInfo): integer;
  132.          begin
  133.             Add := DLLOBJ.Add(Rec,sizeof(Rec));
  134.          end; {RecordDLLOBJ.Add}
  135.          function RecordDLLOBJ.Change(Node:DLLNodePtr;
  136.                                        Rec:RecordInfo): integer;
  137.          begin
  138.             Change := DLLOBJ.Change(Node,Rec,sizeof(Rec));
  139.          end; {RecordDLLOBJ.Change}
  140.  
  141.          function RecordDLLOBJ.InsertBefore(Node:DLLNodePtr;
  142.                                              Rec:RecordInfo): integer;
  143.          {}
  144.          begin
  145.             InsertBefore := DLLOBJ.InsertBefore(Node,Rec,sizeof(Rec));
  146.          end; {RecordDLLOBJ.InsertBefore}
  147.  
  148.          It's really as simple as that.
  149.  
  150.          The function method GetStr is passed three parameters; a node pointer
  151.          indicating which data to access, and the Start and Finish parameters of
  152.          type longint. Start and Finish identify the first and last character
  153.          positions of the sub-string to be returned by the function. The DLLOBJ
  154.          method GetNodeData can be used to retrieve the node data, and then the
  155.          data must be converted into string form. The requested portion of this
  156.          string can then be returned. GetStr could be implemented as follows:
  157.  
  158.  
  159.  
  160. 19-4                                                       Extending the Toolkit
  161. --------------------------------------------------------------------------------
  162.  
  163.          function RecordDLLOBJ.GetStr(Node:DLLNodePtr;Start,Finish: lon-
  164.          gint):string;
  165.          {Returns string representation of record}
  166.          var
  167.            Temp: string;
  168.            Rec: RecordInfo;
  169.          begin
  170.             if Node = nil then
  171.                GetStr := 'Not found'
  172.             else
  173.             begin
  174.                GetNodeData(Node,Rec);  {inherited method}
  175.                with Rec do
  176.                begin
  177.                   Temp := inttostr(ActiveNodeNumber)+': '+
  178.                           FirstName+
  179.                           LastName+
  180.                           Company;
  181.                   if Finish > 53 then
  182.                      Temp := Temp + PicFormat(Tel,'(###) ###-####',' ')+' ';
  183.                   if Finish > 68 then
  184.                      Temp := Temp + JultoStr(LastOrder,MMDDYY)+' ';
  185.                   if Finish > 77 then
  186.                      Temp := Temp + FmtNumberTOT.FormattedReal
  187.                                     (CumDollarsSpent,2,10)+' ';
  188.                   if Finish > 88 then
  189.                      Temp := Temp + Comments;
  190.                 end;
  191.                 GetStr := copy(Temp,Start,succ(Finish-start));
  192.              end;
  193.          end; {RecordDLLOBJ.GetStr}
  194.  
  195.  
  196.          GetStr will never be called with Start and Finish parameters that are
  197.          more than 255 characters apart. In this case example, the entire record
  198.          can be represented by a string, so GetStr builds a string and returns
  199.          the requested sub-string. In cases where the record is larger than will
  200.          fit in a 255 string, the method should only convert the requested por-
  201.          tion of the record in string form. Note that the Browse object calls
  202.          GetStr many times during a browse session, and that GetStr needs to
  203.          respond quickly to avoid sluggishness. If you find that browsing is too
  204.          slow, try the program with range checking, stack checking etc. turned
  205.          off. These compiler directives slow down string-related activity con-
  206.          siderably.
  207.  
  208.          The file EXTLINK.PAS contains the entire code for the customized
  209.          RecordDLLOBJ object, and is only about 100 lines long. Use this unit as
  210.          a template for your own specific record types.
  211.  
  212.  
  213.  
  214.  
  215. Customizing Linked Lists                                                    19-5
  216. --------------------------------------------------------------------------------
  217.  
  218. Displaying RecordListOBJ Records
  219.  
  220.          The List and Browse objects automatically support any descendant of
  221.          DLLOBJ, and so the new object RecordDLLOBJ can be easily displayed in a
  222.          List or Browse window.
  223.          The key method controlling what information is displayed in the window
  224.          is the GetStr method. Thanks to polymorphism, the List and Browse
  225.          objects don't need to know the specifics of GetStr. When they need a
  226.          node in string form, they simply call the DLLOBJ (or descendant) GetStr
  227.          method, and use whichever string is returned.
  228.  
  229.          Listed below is the demo program, EXTDEM5.PAS, which displays the list
  230.          contents in a browse window. Figure 19.1 shows the resultant output.
  231.          Program ExtendedDemoFive;
  232.  
  233.          Uses DOS,CRT,
  234.               totFAST, totINPUT, totList, extLINK, totSTR;
  235.          var
  236.            RecList: RecordDLLOBJ;
  237.            ListWin: BrowseLinkOBJ;
  238.  
  239.          procedure BuildTheList(Filename:string);
  240.          {loads in the data from disk - could also be from d/b}
  241.          var
  242.            F: file of RecordInfo;
  243.            Rec: RecordInfo;
  244.            Ecode: integer;
  245.          begin
  246.             assign(F,filename);
  247.             {$I-}
  248.             reset(F);
  249.             {$I+}
  250.             if ioresult <> 0 then
  251.             begin
  252.                writeln('The file ',filename,' cannot be located.');
  253.                writeln('Demo aborting');
  254.                halt(1);
  255.             end;
  256.             Ecode := 0;
  257.             RecList.Init;
  258.             while not eof(F) and (Ecode = 0) do
  259.             begin
  260.                Read(F,Rec);
  261.                with Rec do
  262.                begin
  263.                   FirstName:= padleft(FirstName,15,' ');
  264.                   LastName:= padleft(LastName,15,' ');
  265.                   Company:= padleft(Company,20,' ');
  266.                end;
  267.  
  268.  
  269.  
  270. 19-6                                                       Extending the Toolkit
  271. --------------------------------------------------------------------------------
  272.  
  273.                Ecode := RecList.Add(Rec);
  274.             end;
  275.             close(F);
  276.          end; {BuildtheList}
  277.  
  278.          begin {Main program}
  279.             BuildTheList('EXTDEM5.DBF');
  280.             Screen.Clear(white,'░'); {paint the screen}
  281.             Key.SetFast;
  282.             with ListWin do
  283.             begin
  284.                Init;
  285.                AssignList(RecList);
  286.                Go;
  287.             end;
  288.          end.
  289.  
  290. Figure 19.1                                                             [SCREEN]
  291. Browsing
  292. RecordDLLOBJ
  293.  
  294.  
  295.          As it stands, the string returned by GetStr is not really suited for
  296.          displaying in a list. Each item in the list would be wider than the
  297.          list display! A quick solution is to build a descendant of RecordDLLStr
  298.          and replace the GetStr method with a method which only returns a short
  299.          string, e.g. last name. The on-disk example EXTDEM6.PAS illustrates
  300.          this technique. In this example, ListLinkOBJ is also extended and cus-
  301.          tomized to show the full record in the message box at the bottom of the
  302.          list. This technique was described on page 9-30. Figure 19.2 shows the
  303.          output generated by EXTDEM6.
  304.  
  305.  
  306. Figure 19.2                                                             [SCREEN]
  307. Listing
  308. RecordDLLOBJ
  309.  
  310.  
  311.  
  312. Sorting
  313.          It is very easy to make your custom linked lists sortable. To make a
  314.          list sortable, the inherited virtual method WrongOrder must be com-
  315.          pleted. Behind the scenes, the Toolkit sort routines repeatedly call
  316.          WrongOrder to determine whether two nodes are in the correct order.
  317.  
  318.          The WrongOrder method is declared as follows:
  319.             function WrongOrder(Node1,Node2:DLLNodePtr;Asc:boolean): boolean;
  320.  
  321.  
  322.  
  323.  
  324. Customizing Linked Lists                                                    19-7
  325. --------------------------------------------------------------------------------
  326.  
  327.          WrongOrder is a boolean function method which should return TRUE if the
  328.          data in the two nodes are in the wrong order. The method is passed
  329.          three parameters; two DLLNodePtr pointers to identify the nodes to be
  330.          compared, and a boolean to indicate whether the list is being sorted in
  331.          ascending (true) or descending (false) order.
  332.  
  333.          The WrongOrder method must therefore get the data from the two nodes,
  334.          and decide if they are in the appropriate order for the sort. All
  335.          DLLOBJ objects include a shortint vSortID. This variable can be used to
  336.          provide multiple sorting capabilities. When you call the Sort method,
  337.          you must pass two parameters; a Sort ID, and a boolean to indicate
  338.          whether the sort order is ascending or descending. The Toolkit automat-
  339.          ically updates vSortID with the parameter passed to Sort. WrongOrder
  340.          should therefore check vSortID to determine what data to use in
  341.          deciding whether the order is correct.
  342.          In the RecordDLLOBJ example, the following codes might be appropriate:
  343.  
  344.                    1      sort on LastName
  345.                    2      sort on Company
  346.                    3      sort on Tel
  347.                    4      sort on CumDollarsSpent
  348.                    5      sort on LastOrder
  349.          The actual codes you select are not important. You decide which fields
  350.          you want to be able to sort on, and which codes to use. Using the
  351.          listed codes, the WrongOrder method for the RecordDLLOBJ object would
  352.          be as follows:
  353.  
  354.          function RecordDLLOBJ.WrongOrder(Node1,Node2:DLLNodePtr;Asc:boolean):
  355.          boolean;
  356.          var
  357.            S1,S2: string;
  358.            Rec1,Rec2: RecordInfo;
  359.            R1,R2: real;
  360.            D1,D2: longint;
  361.          begin
  362.             GetNodeData(Node1,Rec1);
  363.             GetNodeData(Node2,Rec2);
  364.             if vSortID in [1,2,3] then
  365.             begin
  366.                case vSortID of
  367.                   1:begin      {LastName}
  368.                      S1 := Rec1.LastName;
  369.                      S2 := Rec2.LastName;
  370.                   end;
  371.                   2: begin     {Company}
  372.                      S1 := Rec1.Company;
  373.                      S2 := Rec2.Company;
  374.                   end;
  375.                   3: begin     {Tel}
  376.  
  377.  
  378.  
  379. 19-8                                                       Extending the Toolkit
  380. --------------------------------------------------------------------------------
  381.  
  382.                      S1 := Rec1.Tel;
  383.                      S2 := Rec2.Tel;
  384.                   end;
  385.                end; {case}
  386.                if Asc then
  387.                   WrongOrder := (S1 > S2)
  388.                else
  389.                   WrongOrder := (S2 > S1);
  390.             end
  391.             else if vSortID = 4 then  {CumDollars}
  392.             begin
  393.                R1 := Rec1.CumDollarsSpent;
  394.                R2 := Rec2.CumDollarsSpent;
  395.                if Asc then
  396.                   WrongOrder := (R1 > R2)
  397.                else
  398.                   WrongOrder := (R2 > R1);
  399.             end
  400.             else                       {LastOrder}
  401.             begin
  402.                D1 := Rec1.LastOrder;
  403.                D2 := Rec2.LastOrder;
  404.                if Asc then
  405.                   WrongOrder := (D1 > D2)
  406.                else
  407.                   WrongOrder := (D2 > D1);
  408.             end;
  409.          end; {RecordDLLOBJ.WrongOrder}
  410.  
  411.  
  412.          To sort the list, call the method Sort, e.g. MyList.Sort(1,true). The
  413.          demo file EXTDEM6.PAS includes a sort statement.
  414.  
  415.  
  416. Using Status Codes
  417.  
  418.          List status codes are used internally by the Toolkit to support the
  419.          displaying of lists in windows. In some circumstances, you may be able
  420.          to use this facility in your custom lists.
  421.          All items stored in a DLLOBJ, or descendant object, include a Status
  422.          byte. Each of the eight bits in the Status byte can be used for a
  423.          different purpose. Chapter 9: Managing Lists (page 9-27), describes how
  424.          the first two bits are used by the List display object. You may recall
  425.          that bit 0 is used to identify whether the item is tagged, and bit 1
  426.          identifies which items to display in an alternate color. The remaining
  427.          six bits can be used for your own custom needs. For example, you might
  428.          use bit 2 to identify all customers with a delinquent account, and bit
  429.          3 might identify whether the customer is international or domestic, or
  430.  
  431.  
  432.  
  433.  
  434. Customizing Linked Lists                                                    19-9
  435. --------------------------------------------------------------------------------
  436.  
  437.          you might extend ListOBJ to use a three-color list, and use bit 2 to
  438.          identify items to show in the third color. Whatever the reason, they
  439.          are there if you need them.
  440.  
  441.          To access the Status bits, you need to know how data is stored at each
  442.          node. Each node in a list actually points to a DLLNodeOBJ. This object
  443.          manages the storage of data at the node, as well as the node status
  444.          byte. The following four DLLNodeOBJ methods support the Status byte:
  445.                   GetStatus(Bitpos:byte): boolean;
  446.                   SetStatus(BitPos:byte;On:boolean);
  447.                   GetStatusByte:byte;
  448.                   SetStatusByte(Val:byte);
  449.  
  450.          To call these methods use the syntax MyList.NodePtr(number)^.method.
  451.          Use the Set methods to change a status bit, and the get methods to
  452.          check the current status. For example, to set the fourth bit to true
  453.          for the 12th entry in the list MyList, you would use the following
  454.          statement:
  455.                   MyList.NodePtr(12).SetStatus(3,true);
  456.